﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Microsoft.Xna.Framework;
using Kaleta.Xna.Animators;


namespace Kaleta.Xna.Timing
{
    public interface ITimeline
    {
        void Update(GameTime gameTime);
        void Start();

        event EventHandler<TimelineFinishedEventArgs> Finished;
    }


    /// <summary>
    /// This class defines an action which is executed after specified time after Start().
    /// </summary>
    public class ActionTimeline : ITimeline
    {
        /// <summary>
        /// BeginTime specifies the time or delay for this action. If you want a infinite repeation, set repeatBehavior = -1, else specify how many times 
        /// the action should be repeated.
        /// </summary>
        public ActionTimeline(float beginTime, Action animate, int repeatBehavior)
        {
            this.beginTime = beginTime;
            this.animate = animate;
            this.timeStarted = -2; //-2 = timeline initialized; -1 = timeline started, 0 <= timeline started & updating
            this.repeatBehavior = repeatBehavior; //how many times will be animation repeated
        }

        /// <summary>
        /// BeginTime specifies the time or delay for this action. If you want a infinite repeation, set repeatBehavior = -1, else specify how many times 
        /// the action should be repeated. Default is 0, e.g. action is executed only once.
        /// </summary>
        public ActionTimeline(float beginTime, Action animate)
        {
            this.beginTime = beginTime;
            this.animate = animate;
            this.timeStarted = -2; //-2 = timeline initialized; -1 = timeline started, 0 <= timeline started & updating
            this.repeatBehavior = 0; //how many times will be animation repeated
        }

        #region ITimeline Members

        public void Update(GameTime gameTime)
        {
            if (this.timeStarted == -2) return;

            if (timeStarted == -1)
                timeStarted = gameTime.TotalGameTime.TotalMilliseconds;

            if (gameTime.TotalGameTime.TotalMilliseconds - this.timeStarted >= beginTime)
            {
                animate();

                if (this.repeatBehavior == -1)
                    Start();
                else if (this.repeatBehavior > 0)
                {
                    this.repeatBehavior--;
                    Start();
                }
                else
                    if (Finished != null)
                        Finished(this, new TimelineFinishedEventArgs(null));
            }
        }

        /// <summary>
        /// Starts the timeline. After this start, the execution of specified action will proceed after begin time.
        /// </summary>
        public void Start()
        {
            timeStarted = -1;
        }

        public event EventHandler<TimelineFinishedEventArgs> Finished;

        #endregion

        private double timeStarted;
        private float beginTime;
        private Action animate;
        private int repeatBehavior;
    }

    public class IntegerTimeline : Timeline<int>
    {
        public IntegerTimeline(Action<int> animate,
            int from, int to,
            float duration, float beginTime, bool applyBeginTimeBeforeEveryAnimation,
            bool autoReverse, int repeatBehavior)
            : base(animate, from, to, duration, beginTime, applyBeginTimeBeforeEveryAnimation, autoReverse, repeatBehavior)
        { }

        public override void UpdateValue(double updateElapsedTime)
        {
            int valueStep = (int)(((To - From) / Duration) * updateElapsedTime * RunningForward);

            //clamp value:
            if (CurrentValue + valueStep > Math.Max(From, To))
                valueStep = Math.Max(From, To) - CurrentValue;
            else if (CurrentValue + valueStep < Math.Min(From, To))
                valueStep = Math.Min(From, To) - CurrentValue;

            Animate(valueStep);
            CurrentValue += valueStep;
        }
    }

    public class SpriteTimeline : FloatTimeline
    {
        public SpriteTimeline(SpriteAnimator source, float duration, float beginTime, bool applyBeginTimeBeforeEveryAnimation,
            bool autoReverse, int repeatBehavior)
            : base(f => source.FrameIterator += f, 0, source.FrameCount - 1, duration, beginTime, applyBeginTimeBeforeEveryAnimation, autoReverse, repeatBehavior)
        { }
    }

    public class FloatTimeline : Timeline<float>
    {
        public FloatTimeline(Action<float> animate,
            float from, float to,
            float duration, float beginTime, bool applyBeginTimeBeforeEveryAnimation,
            bool autoReverse, int repeatBehavior)
            : base(animate, from, to, duration, beginTime, applyBeginTimeBeforeEveryAnimation, autoReverse, repeatBehavior)
        { }

        public override void UpdateValue(double updateElapsedTime)
        {
            float valueStep = (float)(((To - From) / Duration) * updateElapsedTime * RunningForward);

            //clamp value:
            if (CurrentValue + valueStep > Math.Max(From, To))
                valueStep = Math.Max(From, To) - CurrentValue;
            else if (CurrentValue + valueStep < Math.Min(From, To))
                valueStep = Math.Min(From, To) - CurrentValue;

            Animate(valueStep);
            CurrentValue += valueStep;
        }
    }

    public abstract class Timeline<T> : ITimeline
    {
        public Timeline(Action<T> animate,
            T from, T to,
            float duration, float beginTime, bool applyBeginTimeBeforeEveryAnimation,
            bool autoReverse, int repeatBehavior)
        {

            Animate = animate;
            To = to;
            From = from;
            Duration = duration;
            BeginTime = beginTime;
            ApplyBeginTimeBeforeEveryAnimation = applyBeginTimeBeforeEveryAnimation;
            RepeatBehavior = repeatBehavior;
            AutoReverse = autoReverse;
        }

        public abstract void UpdateValue(double updateElapsedTime); // { }


        public void Update(GameTime gameTime) //gameTime as parameter
        {
            //indicates wheter the WHOLE timeline animation started:
            if (!isTimelineStarted) return;

            //if RunningForward = 0 then it means that this is the 1.Update after the Start():
            if (RunningForward == 0)
            {
                RunningForward = 1;
                TotalElapsedTime = gameTime.TotalGameTime.TotalMilliseconds;
            }

            //indicates wheter a part of timeline animation started:
            if (!IsStarted)
            {
                if (gameTime.TotalGameTime.TotalMilliseconds - TotalElapsedTime < BeginTime)
                    return;
                else
                {
                    IsStarted = true;
                    TotalElapsedTime = gameTime.TotalGameTime.TotalMilliseconds;
                    //Property.SetValue(Source, From, null);
                    CurrentValue = From;
                }
            }

            //get the elapsed time since last update:
            double updateElapsedTime = gameTime.TotalGameTime.TotalMilliseconds - TotalElapsedTime;

            //update total elapsed time:
            TotalElapsedTime = gameTime.TotalGameTime.TotalMilliseconds;

            //update animation total elapsed time:
            ElapsedTime += updateElapsedTime;


            if (ElapsedTime <= Duration)
            {
                UpdateValue(updateElapsedTime);
            }
            else if (AutoReverse == true)
            {
                if (RunningForward == 1)
                {
                    StartReverse(gameTime);
                    TotalElapsedTime -= updateElapsedTime;
                    Update(gameTime);
                }
                else if (RepeatBehavior == -1)
                {
                    StartForward(gameTime);
                    TotalElapsedTime -= updateElapsedTime;
                    Update(gameTime);
                }
                else if (RepeatBehavior > 0)
                {
                    TotalElapsedTime -= updateElapsedTime;
                    RepeatBehavior--;
                    StartForward(gameTime);  //restart
                    Update(gameTime);
                }
                else
                    if (Finished != null)
                        Finished(this, new TimelineFinishedEventArgs(null));

            }
            else if (RepeatBehavior == -1)
            {
                StartForward(gameTime);
                TotalElapsedTime -= updateElapsedTime;
                Update(gameTime);
            }
            else if (RepeatBehavior > 0)
            {
                TotalElapsedTime -= updateElapsedTime;
                RepeatBehavior--;
                StartForward(gameTime);  //restart
                Update(gameTime);
            }
            else
                if (Finished != null)
                    Finished(this, new TimelineFinishedEventArgs(null));
        }

        public void Start()
        {
            this.isTimelineStarted = true;
        }

        /// <summary>
        /// Starts the timeline.
        /// </summary>
        /// <param name="gameTime"></param>
        private void StartForward(GameTime gameTime) //parameter gameTime
        {
            TotalElapsedTime = gameTime.TotalGameTime.TotalMilliseconds;
            ElapsedTime = 0;
            RunningForward = 1;

            if (ApplyBeginTimeBeforeEveryAnimation)
                IsStarted = false;
            else
            {
                //Property.SetValue(Source, From, null);
                CurrentValue = From;
            }

        }

        private void StartReverse(GameTime gameTime) //parameter gameTime
        {
            TotalElapsedTime = gameTime.TotalGameTime.TotalMilliseconds;
            ElapsedTime = 0;
            RunningForward = -1;

            //Property.SetValue(Source, To, null);
            CurrentValue = To;
        }

        /// <summary>
        /// Indicates wheter this timeline has started.
        /// </summary>
        private bool isTimelineStarted = false;

        /// <summary>
        /// Start value.
        /// </summary>
        public T From { get; set; }

        /// <summary>
        /// End value.
        /// </summary>
        public T To { get; set; }

        /// <summary>
        /// Associated object.
        /// </summary>
        //public object Source { get; set; }


        /// <summary>
        /// The action/function which will animate(change) the value of object.
        /// </summary>
        public Action<T> Animate { get; set; }


        private bool IsStarted { get; set; }

        /// <summary>
        /// How long the animation will take in milliseconds.
        /// </summary>
        public float Duration { get; set; }

        /// <summary>
        /// Specifies the start-delay in the very beginning of animation.
        /// </summary>
        public float BeginTime { get; set; }

        /// <summary>
        /// If true, applies the BeginTime on every start of animation (in case of repeating). 
        /// Applies only on forward animation, not reverse (if AutoReverse is true).
        /// </summary>
        public bool ApplyBeginTimeBeforeEveryAnimation { get; set; }

        /// <summary>
        /// If true, after the animation finishes forward loop, a reverse loop is started, meaning animating values To -> From.
        /// </summary>
        public bool AutoReverse { get; set; }

        /// <summary>
        /// Specifies how many times the animation should repeat: 0 is default (no repeat), 1..N means repeat once..N, -1 is forever.
        /// </summary>
        public int RepeatBehavior { get; set; }

        //public PropertyInfo Property { get; set; }

        /// <summary>
        /// Fires if animation has finished.
        /// </summary>
        public event EventHandler<TimelineFinishedEventArgs> Finished;

        protected int RunningForward { get; set; }

        protected T CurrentValue { get; set; }

        /// <summary>
        /// ITimeline total elapsed time.
        /// </summary>
        protected double ElapsedTime { get; set; }

        /// <summary>
        /// World's total elapsed time.
        /// </summary>
        protected double TotalElapsedTime { get; set; }


    }

    public class TimelineFinishedEventArgs : EventArgs
    {
        public object Owner { get; set; }

        public TimelineFinishedEventArgs(object owner)
        {
            Owner = owner;
        }
    }
}
